// MemViewer.java
import java.io.*;
import java.util.*;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.swing.*;
import javax.swing.filechooser.*;
import java.awt.*;
import java.awt.event.*;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

public class MemViewer extends JFrame {
	public static boolean DEBUG = false;
	private JCheckBoxMenuItem debug;
	
    //Optionally set the look and feel.
    private static boolean useSystemLookAndFeel = true;
	
	// to match the line like :
	// 2,UTIL_TEST,2159,4
	public final static String REGX = "^\\s*([0-9]+)\\s*,\\s*([^,]+)\\s*,\\s*([0-9]+)\\s*,\\s*([0-9]+)\\s*";
	
	public static File currentDir;
	public static String selectedCsv;
	
	// status panel
	private StatusPanel statusBar;

	// the main panel which the tree and table are in
	private PanelTreeTable mainPanel;

	// menu items
	private JMenuBar menuBar;
	private JMenu file , help;
	private JMenuItem openDef;
	private JMenuItem search;
	private JMenuItem searchNext;
	private JMenuItem exit;
	private JMenuItem about;
	
	// version info
	public final static String versionStr = "memViewer 0.01\nCopyright (c) 2008.";
	
	// open csv task
	private CsvOpenTask task;

	public static void showVersionInfo(String versionInfo) {
		JOptionPane.showMessageDialog(null,
                versionInfo, "About...",
                JOptionPane.INFORMATION_MESSAGE);
	}
	
	class AboutL implements ActionListener {
		public void actionPerformed(ActionEvent e) {
			showVersionInfo(versionStr);
		}
	}
	
    class ExitL implements ActionListener {
	    public void actionPerformed(ActionEvent e) {
	    	System.exit(0);
	    }
	}
  
    public static void showError(int lineNum, String message) {
		String strTitle = String.format("Error : line -> %d", lineNum);
		JOptionPane.showMessageDialog(null,
				message, strTitle,
                JOptionPane.ERROR_MESSAGE);    	
    }
    
    private void dataTest(Map<Node, HashMap<Node, HashMap<Node, HashMap<Node, Node>>>> allSys) {
    	if (! allSys.isEmpty()) {
    		for(Node sys : allSys.keySet()) {
    			System.out.println(sys + " has:");
    			for(Node mod : allSys.get(sys).keySet()) {
    				System.out.println("  |_" + mod);
    				for(Node obj : allSys.get(sys).get(mod).keySet()) {
        				System.out.println("    |_" + obj);
        				for(Node sec : allSys.get(sys).get(mod).get(obj).keySet()) {
            				System.out.println("      |_" + sec);
        				}
    				}
    			}
    		}
    		
    	}
    }
    
    private Map<Node, HashMap<Node, HashMap<Node, HashMap<Node, Node>>>>
    	convertData(List<String> list) {
    	
    	Pattern p = Pattern.compile(REGX);
    	int lineNum = 0;
    	int totalSizeRam = 0;
    	int totalSizeRom = 0;
    	Node lastSys = null, lastMod = null, lastObj = null, lastSec = null;
    	Map<Node, HashMap<Node, HashMap<Node, HashMap<Node, Node>>>> allSys = 
    		new HashMap<Node, HashMap<Node, HashMap<Node, HashMap<Node, Node>>>>();
   	
    	for(String s : list) {
    		lineNum++;
			Matcher m = p.matcher(s);
		    while(m.find()) {            
	            char c = m.group(1).charAt(0);
	            switch(c) {
		            case '1':
		            	lastSys = new Node(c, m.group(2), Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)));
		            	if ( allSys.containsKey(lastSys) ) {
		            		String message = String.format("[ %s ] appeared more than once!", lastSys.getNodeTitle());
		            		showError(lineNum, message);
		            		return null;
		            	} else {
		            		allSys.put(lastSys, new HashMap<Node, HashMap<Node, HashMap<Node, Node>>>());
		            	}
		            	
		            	// calculate the total ram and rom
		            	totalSizeRom += Integer.parseInt(m.group(3));
		            	totalSizeRam += Integer.parseInt(m.group(4));

		            	if (DEBUG) System.out.println(lastSys);
		            	break;
		            case '2':
		            	if ( null == lastSys ) {
			            	String message = "Input file format error, it's not start from 1 !";
			            	showError(lineNum, message);
			            	return null;
		            	}
		            	lastMod = new Node(c, m.group(2), Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)));
		            	lastMod.setSystem(lastSys);
		            	if (allSys.get(lastSys).containsKey(lastMod)) {
		            		String message = String.format("[ %s ] appeared twice!", lastMod.getNodeTitle());
		            		showError(lineNum, message);
		            		return null;
		            	} else {
		            		allSys.get(lastSys).put(lastMod, new HashMap<Node, HashMap<Node, Node>>());
		            	}

		            	if (DEBUG) System.out.println(lastMod);
		            	break;
		            case '3':
		            	if ( null == lastMod || null == lastSys ) {
			            	String message = "Input file format error, no sub module before object !";
			            	showError(lineNum, message);
			            	return null;
		            	}
		            	lastObj = new Node(c, m.group(2), Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)));
		            	lastObj.setSystem(lastSys);
		            	lastObj.setModule(lastMod);
		            	if (allSys.get(lastSys).get(lastMod).containsKey(lastObj)) {
		            		String message = String.format("[ %s ] appeared twice!", lastObj.getNodeTitle());
		            		showError(lineNum, message);
		            		return null;
		            	} else {
		            		allSys.get(lastSys).get(lastMod).put(lastObj, new HashMap<Node, Node>());
		            	}

		            	if (DEBUG) System.out.println(lastObj);
		            	break;
		            case '4':
		            	if ( null == lastObj || null == lastMod || null == lastSys ) {
			            	String message = "Input file format error, no object before section !";
			            	showError(lineNum, message);
			            	return null;
		            	}
		            	lastSec = new Node(c, m.group(2), Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)));
		            	lastSec.setSystem(lastSys);
		            	lastSec.setModule(lastMod);
		            	lastSec.setObject(lastObj);
		            	if (allSys.get(lastSys).get(lastMod).get(lastObj).containsKey(lastSec)) {
		            		String message = String.format("[ %s ] appeared twice!", lastSec.getNodeTitle());
		            		showError(lineNum, message);
		            		return null;
		            	} else {
		            		allSys.get(lastSys).get(lastMod).get(lastObj).put(lastSec, null);
		            	}

		            	if (DEBUG) System.out.println(lastSec);
		            	break;
		            default:
		            	String message = String.format("%c is illegal!", c);
		            	showError(lineNum, message);
		            	return null;
	            }
		    }
    	}
    	
    	// save the total size
    	mainPanel.setTopNodeSizeRam(totalSizeRam);
    	mainPanel.setTopNodeSizeRom(totalSizeRom);

    	if ( DEBUG ) dataTest(allSys);
    	
    	return allSys;
    }
    
    public static List<String> read(String filename) throws IOException {
    	// Reading input by lines:
    	BufferedReader in = new BufferedReader(new FileReader(filename));
    	String strLine;
    	List<String> list = new LinkedList<String>();
    	
    	while((strLine = in.readLine()) != null) {
    		list.add(strLine);
    	}
    	
    	in.close();
    	return list;
    }
    
    class CheckMenuBoxL implements ItemListener {
    	public void itemStateChanged(ItemEvent e) {
          JCheckBoxMenuItem target =
            (JCheckBoxMenuItem)e.getSource();
          
          if ( target.getState() ) {
        	  DEBUG = true;
          } else {
        	  DEBUG = false;
          }
        }
    }
    
    private void openCsv() {
		// to open the csv file
		JFileChooser fileChooser = new JFileChooser(".");
		if ( null !=  currentDir ) fileChooser.setCurrentDirectory(currentDir);
		FileNameExtensionFilter filter = new FileNameExtensionFilter(".csv files", "csv");
		fileChooser.setFileFilter(filter);
		
		Map<Node, HashMap<Node, HashMap<Node, HashMap<Node, Node>>>> allSys;
		
		int rVal = fileChooser.showOpenDialog(MemViewer.this);
		if(rVal == JFileChooser.APPROVE_OPTION) {
			currentDir = fileChooser.getCurrentDirectory();
		    selectedCsv = fileChooser.getSelectedFile().toString();
		    
		    try {
		    	allSys = convertData(read(selectedCsv));
		    } catch (IOException err) {
		    	String title = "Error : " + selectedCsv + " not found!";
				JOptionPane.showMessageDialog(null,	err.toString(), title, JOptionPane.ERROR_MESSAGE);
				return;
		    }
		    
		    mainPanel.setData(allSys);
		                
		    // set the status bar
		    Integer sra = mainPanel.getTopNodeSizeRam();
		    Integer sro = mainPanel.getTopNodeSizeRom();
		    statusBar.setNodeTitle("Total");
		    statusBar.setSizeRam(sra.toString());
		    statusBar.setSizeRom(sro.toString());
		}
    }

    /**
     * Invoked when task's progress property changes.
     */
    class PropertyChangeL implements PropertyChangeListener {
	    public void propertyChange(PropertyChangeEvent evt) {
	        if ("progress" == evt.getPropertyName()) {
	            int progress = (Integer) evt.getNewValue();
	            statusBar.getProgressBar().setIndeterminate(false);
	            statusBar.setProgress(progress);
	        }
	    }
    }
    
    class CsvOpenTask extends SwingWorker<Void, Void> {
    	
    	
        /*
         * Main task. Executed in background thread.
         */
        @Override
        public Void doInBackground() {
            
            int progress = 0;
            //Initialize progress property.
            setProgress(progress);

    		// to open the csv file
    		JFileChooser fileChooser = new JFileChooser(".");
    		if ( null !=  currentDir ) fileChooser.setCurrentDirectory(currentDir);
    		FileNameExtensionFilter filter = new FileNameExtensionFilter(".csv files", "csv");
    		fileChooser.setFileFilter(filter);
    		
	    	Map<Node, HashMap<Node, HashMap<Node, HashMap<Node, Node>>>> allSys = 
	    		new HashMap<Node, HashMap<Node, HashMap<Node, HashMap<Node, Node>>>>();
    		
    		int rVal = fileChooser.showOpenDialog(MemViewer.this);
    		if(rVal == JFileChooser.APPROVE_OPTION) {
    			synchronized (this) {
	    			currentDir = fileChooser.getCurrentDirectory();
	    		    selectedCsv = fileChooser.getSelectedFile().toString();
    			}
    			
    		    List<String> list;
    		    
    		    try {
    		    	list = read(selectedCsv);
    		    } catch (IOException err) {
    		    	String title = "Error : " + selectedCsv + " not found!";
    				JOptionPane.showMessageDialog(null,	err.toString(), title, JOptionPane.ERROR_MESSAGE);
    				return null;
    		    }
    		    
    		    //statusBar.setProgressRange(0, list.size());
    		    
    	    	Pattern p = Pattern.compile(REGX);
    	    	int lineNumber = 0;
    	    	int totalSizeRam = 0;
    	    	int totalSizeRom = 0;
    	    	int step = 5;
    	    	Node lastSys = null, lastMod = null, lastObj = null, lastSec = null;
    	    	
    	    	// set the progress step
    	    	if (list.size() > 100 ) {
    	    		step = list.size()/100;
    	    	}
    	    	
    	    	for(String s : list) {
    	    		lineNumber++;
    				Matcher m = p.matcher(s);
    			    while(m.find()) {            
    		            char c = m.group(1).charAt(0);
    		            switch(c) {
    			            case '1':
    			            	lastSys = new Node(c, m.group(2), Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)));
    			            	if ( allSys.containsKey(lastSys) ) {
    			            		String message = String.format("[ %s ] appeared more than once!", lastSys.getNodeTitle());
    			            		showError(lineNumber, message);
    			            		setProgress(100);
    			            		return null;
    			            	} else {
    			            		allSys.put(lastSys, new HashMap<Node, HashMap<Node, HashMap<Node, Node>>>());
    			            	}
    			            	
    			            	// calculate the total ram and rom
    			            	totalSizeRom += Integer.parseInt(m.group(3));
    			            	totalSizeRam += Integer.parseInt(m.group(4));

    			            	if (DEBUG) System.out.println(lastSys);
    			            	break;
    			            case '2':
    			            	if ( null == lastSys ) {
    				            	String message = "Input file format error, it's not start from 1 !";
    				            	showError(lineNumber, message);
    				            	setProgress(100);
    				            	return null;
    			            	}
    			            	lastMod = new Node(c, m.group(2), Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)));
    			            	lastMod.setSystem(lastSys);
    			            	if (allSys.get(lastSys).containsKey(lastMod)) {
    			            		String message = String.format("[ %s ] appeared twice!", lastMod.getNodeTitle());
    			            		showError(lineNumber, message);
    			            		setProgress(100);
    			            		return null;
    			            	} else {
    			            		allSys.get(lastSys).put(lastMod, new HashMap<Node, HashMap<Node, Node>>());
    			            	}

    			            	if (DEBUG) System.out.println(lastMod);
    			            	break;
    			            case '3':
    			            	if ( null == lastMod || null == lastSys ) {
    				            	String message = "Input file format error, no sub module before object !";
    				            	showError(lineNumber, message);
    				            	setProgress(100);
    				            	return null;
    			            	}
    			            	lastObj = new Node(c, m.group(2), Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)));
    			            	lastObj.setSystem(lastSys);
    			            	lastObj.setModule(lastMod);
    			            	if (allSys.get(lastSys).get(lastMod).containsKey(lastObj)) {
    			            		String message = String.format("[ %s ] appeared twice!", lastObj.getNodeTitle());
    			            		showError(lineNumber, message);
    			            		setProgress(100);
    			            		return null;
    			            	} else {
    			            		allSys.get(lastSys).get(lastMod).put(lastObj, new HashMap<Node, Node>());
    			            	}

    			            	if (DEBUG) System.out.println(lastObj);
    			            	break;
    			            case '4':
    			            	if ( null == lastObj || null == lastMod || null == lastSys ) {
    				            	String message = "Input file format error, no object before section !";
    				            	showError(lineNumber, message);
    				            	setProgress(100);
    				            	return null;
    			            	}
    			            	lastSec = new Node(c, m.group(2), Integer.parseInt(m.group(3)), Integer.parseInt(m.group(4)));
    			            	lastSec.setSystem(lastSys);
    			            	lastSec.setModule(lastMod);
    			            	lastSec.setObject(lastObj);
    			            	if (allSys.get(lastSys).get(lastMod).get(lastObj).containsKey(lastSec)) {
    			            		String message = String.format("[ %s ] appeared twice!", lastSec.getNodeTitle());
    			            		showError(lineNumber, message);
    			            		setProgress(100);
    			            		return null;
    			            	} else {
    			            		allSys.get(lastSys).get(lastMod).get(lastObj).put(lastSec, null);
    			            	}

    			            	if (DEBUG) System.out.println(lastSec);
    			            	break;
    			            default:
    			            	String message = String.format("%c is illegal!", c);
    			            	showError(lineNumber, message);
    			            	setProgress(100);
    			            	return null;
    		            }
    			    }
    			    if ( lineNumber % step == 0 ) {
    			    	float percent = (lineNumber * 100)/list.size();
    			    	progress = (int)percent;
    			    	setProgress(progress);
    			    }
    			    
    	    	}

    	    	// save the total size
    	    	mainPanel.setTopNodeSizeRam(totalSizeRam);
    	    	mainPanel.setTopNodeSizeRom(totalSizeRom);

    	    	if ( DEBUG ) dataTest(allSys);
    		    
    		    mainPanel.setData(allSys);
    		}
    		
    		setProgress(100);
            
            return null;
        }

        /*
         * Executed in event dispatch thread
         */
        public void done() {
        	search.setEnabled(true);
        	
		    // set the status bar
		    Integer sra = mainPanel.getTopNodeSizeRam();
		    Integer sro = mainPanel.getTopNodeSizeRom();
		    statusBar.setNodeTitle("Total");
		    statusBar.setSizeRam(sra.toString());
		    statusBar.setSizeRom(sro.toString());
        }
    }
    
    class OpenL implements ActionListener {
        public void actionPerformed(ActionEvent e) {
        	// no progressbar method
        	//openCsv();
        	
        	// progressbar related
            statusBar.getProgressBar().setIndeterminate(true);
            //Instances of javax.swing.SwingWorker are not reusable, so
            //we create new instances as needed.
            task = new CsvOpenTask();
            task.addPropertyChangeListener(new PropertyChangeL());
            task.execute();   
        }
    }

    private boolean searchNode() {
        String s = (String)JOptionPane.showInputDialog(
                this,
                "input the word you want to search :",
                "Search...",
                JOptionPane.PLAIN_MESSAGE,
                null,
                null,
                "util");

		// If a string was returned, say so.
		if ((s != null) && (s.length() > 0)) {
			mainPanel.searchAllNodesWithStr(s);
			return true;
		}

		return false;
    }
    
    class SearchL implements ActionListener {
        public void actionPerformed(ActionEvent e) {
        	if (searchNode()) searchNext.setEnabled(true);
        }
    }
    
    class SearchNextL implements ActionListener {
        public void actionPerformed(ActionEvent e) {
        	mainPanel.searchNext();
        }
    }
    
	public MemViewer() {
		
		// add the status panel before the panel, it's needed for panel initialization
		statusBar = new StatusPanel();
		add(BorderLayout.SOUTH, statusBar);
		
		debug = new JCheckBoxMenuItem("Debug...");
		debug.setAccelerator(KeyStroke.getKeyStroke(
                KeyEvent.VK_D, ActionEvent.CTRL_MASK));
		mainPanel = new PanelTreeTable(statusBar);
		menuBar = new JMenuBar();
		file = new JMenu("File    ");
		file.setMnemonic(KeyEvent.VK_F);
		help = new JMenu("Help    ");
		help.setMnemonic(KeyEvent.VK_H);
		openDef = new JMenuItem("Open def database...");
		openDef.setAccelerator(KeyStroke.getKeyStroke(
                KeyEvent.VK_O, ActionEvent.CTRL_MASK));
		search = new JMenuItem("Search...");
		search.setAccelerator(KeyStroke.getKeyStroke(
                KeyEvent.VK_F, ActionEvent.CTRL_MASK));
		search.setEnabled(false);
		searchNext = new JMenuItem("Search Next...");
		searchNext.setAccelerator(KeyStroke.getKeyStroke(
                KeyEvent.VK_N, ActionEvent.CTRL_MASK));
		searchNext.setEnabled(false);
		exit = new JMenuItem("Exit");
		exit.setAccelerator(KeyStroke.getKeyStroke(
                KeyEvent.VK_E, ActionEvent.CTRL_MASK));
		about = new JMenuItem("About...");
		about.setAccelerator(KeyStroke.getKeyStroke(
                KeyEvent.VK_H, ActionEvent.CTRL_MASK));
		
		currentDir = null; // current directory
		selectedCsv = null;
		menuBar.add(file);
		file.add(openDef);
		file.add(search);
		file.add(searchNext);
		file.addSeparator();
		file.add(exit);
		openDef.addActionListener(new OpenL());
		exit.addActionListener(new ExitL());
		search.addActionListener(new SearchL());
		searchNext.addActionListener(new SearchNextL());
		menuBar.add(help);
		help.add(about);
		debug.setMnemonic(KeyEvent.VK_D);
		debug.addItemListener(new CheckMenuBoxL());
		help.add(debug);
		about.addActionListener(new AboutL());
		setJMenuBar(menuBar);
	
		// add the main panel for tree and table
		this.add(mainPanel);

	}
	
	public static void main(String[] args) {
		
        if (useSystemLookAndFeel) {
            try {
                UIManager.setLookAndFeel(
                    UIManager.getSystemLookAndFeelClassName());
            } catch (Exception e) {
                System.err.println("Couldn't use system look and feel.");
            }
        }  
		
		SwingConsole.run(new MemViewer(), 700, 500);
	}
}
